
#include <cassert>

#include "Core\Camera.h"
#include "Core\Globals.h"

Camera::Camera()
	: m_fov(D3DX_PIF / 4.0f),
	  m_aspect(800.0f / 600.0f),
	  m_zmin(0.1f),
	  m_zmax(1000.0f),
	  m_projection(Matrix4x4::CreateIdentity()),
	  m_view(Matrix4x4::CreateIdentity())
{
}


Camera::Camera(FLOAT fov, FLOAT width, FLOAT height, FLOAT zmin, FLOAT zmax)
	: m_fov(fov),
	  m_aspect(width / height),
	  m_zmin(zmin),
	  m_zmax(zmax),
	  m_projection(Matrix4x4::CreateIdentity()),
	  m_view(Matrix4x4::CreateIdentity())
{
}


Camera::Camera(FLOAT fov, FLOAT aspect, FLOAT zmin, FLOAT zmax)
	: m_fov(fov),
	  m_aspect(aspect),
	  m_zmin(zmin),
	  m_zmax(zmax),
	  m_projection(Matrix4x4::CreateIdentity()),
	  m_view(Matrix4x4::CreateIdentity())
{
}


Camera::~Camera()
{
}


Camera::Camera(const Camera& other)
	: m_fov(other.m_fov),
	  m_aspect(other.m_aspect),
	  m_zmin(other.m_zmin),
	  m_zmax(other.m_zmax),
	  m_projection(other.m_projection),
	  m_view(other.m_view)
{
}


Camera& Camera::operator = (const Camera& other)
{
	// Self-assignement.
	if(this == &other)
		return *this;

	// Copy.
	m_fov = other.m_fov;
	m_aspect = other.m_aspect;
	m_zmin = other.m_zmin;
	m_zmax = other.m_zmax;
	m_view = other.m_view;

	return *this;
}


FLOAT Camera::FieldOfView() const
{
	return m_fov;
}


VOID Camera::SetFieldOfView(FLOAT fov)
{
	// Field of view has to be greater then 0.
	assert(fov > 0);

	m_fov = fov;
}


FLOAT Camera::Aspect() const
{
	return m_aspect;
}


VOID Camera::SetAspect(FLOAT aspect)
{
	// Aspect has to be greater then 0.
	assert(aspect > 0);

	m_aspect = aspect;
}


VOID Camera::SetAspect(FLOAT width, FLOAT height)
{
	assert(width * height > 0);

	SetAspect(width / height);
}


FLOAT Camera::Near() const
{
	return m_zmin;
}


VOID Camera::SetNear(FLOAT z)
{
	// Near plane has to be in front of eye.
	assert(z > 0);

	m_zmin = z;
}


FLOAT Camera::Far() const
{
	return m_zmax;
}


VOID Camera::SetFar(FLOAT z)
{
	// Near plane has to be in front of eye.
	assert(z > 0);

	m_zmax = z;
}


Vector4 Camera::GetPosition() const
{
	Vector4 position = m_view.GetColumn(3);

	// Position in space has to be defined by a point.
	assert(position.IsPoint());

	return position;
}


VOID Camera::SetPosition(const Vector4& position)
{
	// Only a point can define a position.
	assert(position.IsPoint());

	m_view.SetColumn(3, position);
}


VOID Camera::SetPosition(FLOAT x, FLOAT y, FLOAT z)
{
	m_view.SetColumn(3, x, y, z, 1);
}


Vector4 Camera::GetRotations() const
{
	return Vector4(asin(-m_view(0, 2)), atan(m_view(0, 1) / m_view(0, 0)), atan(m_view(1, 2) / m_view(2, 2)), 0.0f);
}


VOID Camera::SetRotations(const Vector4& rotation)
{
	// Only rotation vector can define rotations.
	assert(rotation.IsVector());

	FLOAT cos_x = cosf(rotation.GetX());
	FLOAT cos_y = cosf(rotation.GetY());
	FLOAT cos_z = cosf(rotation.GetZ());
	FLOAT sin_x = sinf(rotation.GetX());
	FLOAT sin_y = sinf(rotation.GetY());
	FLOAT sin_z = sinf(rotation.GetZ());

	m_view(0, 0) = cos_y * cos_z;
	m_view(0, 1) = cos_x * sin_z + sin_x * sin_y * cos_z;
	m_view(0, 2) = sin_x * sin_z - cos_x * sin_y * cos_z;
	m_view(1, 0) = -cos_y * sin_z;
	m_view(1, 1) = cos_x * cos_z - sin_x * sin_y * sin_z;
	m_view(1, 2) = sin_x * cos_z + cos_x * sin_y * sin_z;
	m_view(2, 0) = sin_y;
	m_view(2, 1) = -sin_x * cos_y;
	m_view(2, 2) = cos_x * cos_y;
}


VOID Camera::SetRotations(FLOAT attitude, FLOAT heading, FLOAT bank)
{
	FLOAT cos_x = cosf(attitude);
	FLOAT cos_y = cosf(heading);
	FLOAT cos_z = cosf(bank);
	FLOAT sin_x = sinf(attitude);
	FLOAT sin_y = sinf(heading);
	FLOAT sin_z = sinf(bank);

	m_view(0, 0) = cos_y * cos_z;
	m_view(0, 1) = cos_x * sin_z + sin_x * sin_y * cos_z;
	m_view(0, 2) = sin_x * sin_z - cos_x * sin_y * cos_z;
	m_view(1, 0) = -cos_y * sin_z;
	m_view(1, 1) = cos_x * cos_z - sin_x * sin_y * sin_z;
	m_view(1, 2) = sin_x * cos_z + cos_x * sin_y * sin_z;
	m_view(2, 0) = sin_y;
	m_view(2, 1) = -sin_x * cos_y;
	m_view(2, 2) = cos_x * cos_y;
}


VOID Camera::Rotate(const Vector4& axis, FLOAT angle)
{
	Vector4 offset = m_view.GetColumn(3);

	m_view.SetColumn(3, 0.0f, 0.0f, 0.0f, 1.0f);
	m_view *= Matrix4x4::CreateRotation(axis, angle);
	m_view.SetColumn(3, offset);
}


VOID Camera::Rotate(FLOAT x, FLOAT y, FLOAT z, FLOAT angle)
{
	Vector4 offset = m_view.GetColumn(3);

	m_view.SetColumn(3, 0.0f, 0.0f, 0.0f, 1.0f);
	m_view *= Matrix4x4::CreateRotation(x, y, z, angle);
	m_view.SetColumn(3, offset);
}


VOID Camera::Translate(const Vector4& vector)
{
	// Translation vector cannot be a point.
	assert(vector.IsVector());

	m_view *= Matrix4x4::CreateTranslation(vector);
}


VOID Camera::Translate(FLOAT x, FLOAT y, FLOAT z)
{
	m_view *= Matrix4x4::CreateTranslation(x, y, z);
}


VOID Camera::LookAt(const Vector4& target)
{
	// TODO : Implement.
}


VOID Camera::LookAt(FLOAT x, FLOAT y, FLOAT z)
{
	// TODO : Implement.
}


const Matrix4x4& Camera::GetProjection() const
{
	UpdateProjection();

	return m_projection;
}


const Matrix4x4& Camera::GetView() const
{
	m_view = m_view.Orthonormalize();

	return m_view;
}


VOID Camera::UpdateProjection() const
{
	D3DXMATRIX projection;
	D3DXMatrixPerspectiveFovLH(&projection, m_fov, m_aspect, m_zmin, m_zmax);

	for(UINT row = 0; row < 4; ++row)
		for(UINT column = 0; column < 4; ++column)
			m_projection(column, row) = projection(row, column);
}